#include "fyp/FypApp.h"

//------------------------------------------------------------------------------
// additional header includes
#include <windowsx.h>

#include "fyp/render/Factory.h"
#include "fyp/render/Renderer.h"
#include "fyp/render/ResourceFactory.h"
#include "fyp/render/Shader.h"

#include "fyp/GuiManager.h"
#include "fyp/ObjectManager.h"
#include "fyp/ResourcePool.h"
#include "fyp/Camera.h"
#include "fyp/FypAppGui.h"

#include "fyp/shadows/None.h"
#include "fyp/shadows/SSM.h"
#include "fyp/shadows/PCF.h"
#include "fyp/shadows/BiPCF.h"
#include "fyp/shadows/BiPCF2.h"
#include "fyp/shadows/VSM.h"
#include "fyp/shadows/VarPCF.h"
#include "fyp/shadows/VarPCF2.h"
#include "fyp/shadows/VarPCF3.h"
#include "fyp/shadows/VarPCF4.h"
#include "fyp/shadows/VarPCF5.h"
#include "fyp/shadows/DVSM.h"

#include "dxut.h"

//------------------------------------------------------------------------------
namespace ming
{
namespace fyp
{
	//--------------------------------------------------------------------------
	TFypApp::TFypApp() :
		mRenderer(NULL),
		mResFactory(NULL),
		mGuiMgr(NULL),
		mObjMgr(NULL),
		mResPool(NULL),
		mEyeCam(new TCamera),
		mLightCam(new TCamera),
		mLightRotate(false),
		mCurrAlgorithm(0),
		mCurrShadowMapSize(1),
		mShadowMapBias(0.00015f),
		mMouseDelta(0.0f, 0.0f, 0.0f)
	{
		mAlgorithms.push_back( new shadows::TNone );
		mAlgorithms.push_back( new shadows::TDVSM );
		mAlgorithms.push_back( new shadows::TSSM );
		mAlgorithms.push_back( new shadows::TPCF );
		//mAlgorithms.push_back( new shadows::TBiPCF );
		mAlgorithms.push_back( new shadows::TBiPCF2 );
		mAlgorithms.push_back( new shadows::TVSM );
		//mAlgorithms.push_back( new shadows::TVarPCF );
		//mAlgorithms.push_back( new shadows::TVarPCF2 );
		//mAlgorithms.push_back( new shadows::TVarPCF3 );
		mAlgorithms.push_back( new shadows::TVarPCF4 );
		mAlgorithms.push_back( new shadows::TVarPCF5 );
		

		mShadowMapSizes.push_back(64);
		mShadowMapSizes.push_back(128);
		mShadowMapSizes.push_back(256);
		mShadowMapSizes.push_back(512);
		mShadowMapSizes.push_back(1024);

		mCurrShadowMapSize = 3;

		get<0>(mBgColor) = TVec4(0.45f, 0.45f, 0.5f, 1.0f);
		get<1>(mBgColor) = TVec4(0.1f, 0.1f, 0.1f, 1.0f);
		//get<0>(mBgColor) = TVec4(1.0f, 1.0f, 1.0f, 1.0f);
		//get<1>(mBgColor) = TVec4(1.0f, 1.0f, 1.0f, 1.0f);

		mEyeCam->Yaw( Math::deg2Rad(50.0f) );
		mEyeCam->Pitch( Math::deg2Rad(-25.0f) );
		//mLightCam->Yaw( Math::deg2Rad(45.0f) );
		mLightCam->Pitch( Math::deg2Rad(5.0f) );
		
		mEyeCam->SetTargetPosition( TVec3(0, 0, 0), 90.0f );
		mLightCam->SetTargetPosition( TVec3(0, 0, 0), 30.0f );

		mActiveCam = mEyeCam;
	}
	
	//--------------------------------------------------------------------------
	TFypApp::~TFypApp()
	{
		for(size_t i=0; i<mAlgorithms.size(); ++i)
			delete mAlgorithms[i];

		delete mLightCam;
		delete mEyeCam;
	}

	//--------------------------------------------------------------------------
	TFypApp::WndInfo TFypApp::GetAppWndInfo()
	{
		return WndInfo(
	#if defined _DEBUG || defined DEBUG
			(const WCHAR*)L"Real Time Soft Shadow Demo [Debug Mode]",
	#else
			(const WCHAR*)L"Real Time Soft Shadow Rendering",
	#endif
			true,
			(int)800,
			(int)600);
	}

	//--------------------------------------------------------------------------
	bool TFypApp::AcceptDevice(
		D3DCAPS9* caps,
		D3DFORMAT adapterFmt,
		D3DFORMAT backBufFmt)
	{
		if( caps->PixelShaderVersion < D3DPS_VERSION(3, 0) )
			return false;

		if( backBufFmt != D3DFMT_A8R8G8B8)
			return false;

		return true;
	}

	//--------------------------------------------------------------------------
	void TFypApp::SetDevice(IDirect3DDevice9* device)
	{
		mRenderer = render::CreateRenderer(device);
		RegisterShaderParams();

		mResFactory = render::CreateResFactory(device);
		mResPool = new TResourcePool(mResFactory);
		mObjMgr = new TObjectManager(mResPool);
		mGuiMgr = new TGuiManager(device);

		mGui = new TFypAppGui(this, mGuiMgr);

		mResPool->Add<render::IShader>(
			"Visualization",
			make_tuple( STR("../media/shader/Visualization.fx"), STR("Visualization") ) );

		mResPool->Add<render::IShader>(
			"GradientFill",
			make_tuple( STR("../media/shader/GradientFill.fx"), STR("GradientFill") ) );

		InitCurrentAlgorithm();
	}

	//--------------------------------------------------------------------------
	void TFypApp::RecreateResources(
		IDirect3DDevice9* device,
		const D3DSURFACE_DESC* backBufDesc)
	{
		mBackBufDesc = *backBufDesc;

		mGuiMgr->ReloadResouces();

		// set background color
		mResPool->Get<render::IShader>("GradientFill")->BindParam("UPPER_COLOR", &get<0>(mBgColor), sizeof(TVec4) );
		mResPool->Get<render::IShader>("GradientFill")->BindParam("LOWER_COLOR", &get<1>(mBgColor), sizeof(TVec4) );

		// setup eye camera param
		mEyeCam->SetViewParams(
			Math::PI_OVER_4,
			(float)mBackBufDesc.Width / mBackBufDesc.Height,
			1.0f,
			500.0f);

		// setup light camera param
		mLightCam->SetViewParams(
			Math::deg2Rad(60.0f),
			1.0f,
			0.01f,
			100.0f);
	}

	//--------------------------------------------------------------------------
	void TFypApp::DestroyResources()
	{
		FinalCurrentAlgorithm();

		delete mGui;

		// delete temp RT
		delete mResPool;
		delete mObjMgr;
		delete mGuiMgr;
		delete mRenderer;
		delete mResFactory;
	}

	//--------------------------------------------------------------------------
	enum TFypApp::TShaderParams
	{
		AMBIENT_COLOR = render::IRenderer::LAST_DEFAULT_SHADER_ID + 1,
		CAMERA_POS,
		LIGHT_POS,
		LIGHT_PLANE,
		LIGHTVIEW_MTX,
		LIGHTVIEWPROJ_MTX,
		LIGHTVIEWPROJTEX_MTX,
		SHADOWMAP_SIZE,
		INV_SHADOWMAP_SIZE,
		SHADOWMAP_BIAS,
	};

	//--------------------------------------------------------------------------
	void TFypApp::RegisterShaderParams()
	{
		mRenderer->RegisterGlobalShaderParam(AMBIENT_COLOR, "AMBIENT_COLOR");
		mRenderer->RegisterGlobalShaderParam(CAMERA_POS, "CAMERA_POS");
		mRenderer->RegisterGlobalShaderParam(LIGHT_POS, "LIGHT_POS");
		mRenderer->RegisterGlobalShaderParam(LIGHT_PLANE, "LIGHT_PLANE");
		mRenderer->RegisterGlobalShaderParam(LIGHTVIEW_MTX, "LIGHTVIEW_MTX");
		mRenderer->RegisterGlobalShaderParam(LIGHTVIEWPROJ_MTX, "LIGHTVIEWPROJ_MTX");
		mRenderer->RegisterGlobalShaderParam(LIGHTVIEWPROJTEX_MTX, "LIGHTVIEWPROJTEX_MTX");
		mRenderer->RegisterGlobalShaderParam(SHADOWMAP_SIZE, "SHADOWMAP_SIZE");
		mRenderer->RegisterGlobalShaderParam(INV_SHADOWMAP_SIZE, "INV_SHADOWMAP_SIZE");
		mRenderer->RegisterGlobalShaderParam(SHADOWMAP_BIAS, "SHADOWMAP_BIAS");

	}

	//--------------------------------------------------------------------------
	void TFypApp::UpdateShaderParams()
	{
		mRenderer->SetGlobalShaderParam( render::IRenderer::VIEW_MTX, mEyeCam->GetViewMatrix() );
		mRenderer->SetGlobalShaderParam( render::IRenderer::PROJ_MTX, mEyeCam->GetProjMatrix() );

		mRenderer->SetGlobalShaderParam( AMBIENT_COLOR, TVec4(0.075f, 0.075f, 0.075f, 1.0f) );
		mRenderer->SetGlobalShaderParam( CAMERA_POS, mEyeCam->GetViewPosition() );
		mRenderer->SetGlobalShaderParam( LIGHT_POS, mLightCam->GetViewPosition() );
		mRenderer->SetGlobalShaderParam( LIGHT_PLANE, mLightCam->GetViewNearPlaneNormalized() );

		
		TMtx44 lightView = mLightCam->GetViewMatrix();
		TMtx44 lightViewProj = lightView * mLightCam->GetProjMatrix();
		TMtx44 texMtx;
		
		texMtx.set(
			0.5f, 0.0f, 0.0f, 0.0f,
			0.0f,-0.5f, 0.0f, 0.0f,
			0.0f, 0.0f, 1.0f, 0.0f,
			0.5f, 0.5f, 0.0f, 1.0f);


		mRenderer->SetGlobalShaderParam( LIGHTVIEW_MTX, lightView );
		mRenderer->SetGlobalShaderParam( LIGHTVIEWPROJ_MTX, lightViewProj );
		mRenderer->SetGlobalShaderParam( LIGHTVIEWPROJTEX_MTX, lightViewProj * texMtx );

		float invSMapSize = 1.0f / (float)CurrentShadowMapSize();

		mRenderer->SetGlobalShaderParam( SHADOWMAP_SIZE, (float)CurrentShadowMapSize());
		mRenderer->SetGlobalShaderParam( INV_SHADOWMAP_SIZE, TVec2(invSMapSize, 0.5f * invSMapSize) );
		mRenderer->SetGlobalShaderParam( SHADOWMAP_BIAS, mShadowMapBias);
	}

	//--------------------------------------------------------------------------
	void TFypApp::Render(IDirect3DDevice9* device, float dt)
	{
		// update
		StepEye(dt);
		mObjMgr->Step(dt);

		mGui->UpdateFrameRate();

		// update shader params
		UpdateShaderParams();

		// render
		mRenderer->BeginRender();

		mRenderer->ClearDepth(1.0f);

		mRenderer->RenderScreenQuad( mResPool->Get<render::IShader>("GradientFill") );

		RenderScene();
		RenderVisualizations();

		mGuiMgr->Render(dt);

		mRenderer->EndRender();
		
	}

	//--------------------------------------------------------------------------
	void TFypApp::RenderScene()
	{
		mAlgorithms[mCurrAlgorithm]->RenderScene(mRenderer, mResPool, mObjMgr);
	}

	//--------------------------------------------------------------------------
	void TFypApp::RenderVisualizations()
	{
		render::IShader *shader = mResPool->Get<render::IShader>("Visualization");

		mObjMgr->RenderBasisVisualization(mRenderer, shader);

		TMtx44 lightView = mLightCam->GetViewMatrix();
		invert(lightView);

		mObjMgr->RenderLightVisualization(mRenderer, shader, lightView);
	}

	//--------------------------------------------------------------------------
	void TFypApp::HandleMouseEvent(
		bool leftBtnDown,
		bool rightBtnDown,
		bool midBtnDown,
		int wheelDelta,
		int xPos,
		int yPos)
	{
		float dx = (float)xPos;
		float dy = (float)yPos;
	}

	//--------------------------------------------------------------------------
	void TFypApp::HandleKeyboardEvent(
		unsigned int charCode,
		bool keyDown,
		bool altDown)
	{
		if(keyDown)
		{
			switch(charCode)
			{
			case VK_SPACE:
				mObjMgr->NextCasterObject();
				break;

			/*
			case VK_RETURN:
				NextShadowMapSize();
				break;

			case VK_F1:
				NextAlgorithm();
				break;
			*/

			case VK_F1:
				mGuiMgr->SetVisible( !mGuiMgr->IsVisible() );
				break;

			default:
				break;
			}
		}
	}

	//--------------------------------------------------------------------------
	void TFypApp::HandleWindowEvent(
		HWND hWnd,
		UINT uMsg,
		WPARAM wParam,
		LPARAM lParam,
		bool* pbNoFurtherProcessing)
	{
		if(mGuiMgr)
		{
			mGuiMgr->HandleWindowEvent(hWnd, uMsg, wParam, lParam, pbNoFurtherProcessing);
		}

		if(! *pbNoFurtherProcessing )
		{
			if(uMsg == WM_MOUSEMOVE)
			{
				static int lastX = 0;
				static int lastY = 0;

				int x = (int)GET_X_LPARAM(lParam);
				int y = (int)GET_Y_LPARAM(lParam);

				if( DXUTIsKeyDown(VK_LMENU) )
				{
					float dx = (float)x - lastX;
					float dy = (float)y - lastY;

					if(wParam & MK_LBUTTON)
					{
						mMouseDelta[0] += dx;
						mMouseDelta[1] += dy;
					}

					if(wParam & MK_RBUTTON)
					{
						mMouseDelta[2] += dy;
					}
				}

				lastX = x;
				lastY = y;
			}
		}
	}

	//--------------------------------------------------------------------------
	void TFypApp::StepEye(float dt)
	{
		mActiveCam->Yaw( -mMouseDelta[0] * dt * 2.0f);
		mActiveCam->Pitch( mMouseDelta[1] * dt * 2.0f);

		if(mActiveCam == mEyeCam)
			mActiveCam->Forward( mMouseDelta[2] * 2.0f );

		mMouseDelta = TVec3(0.0f, 0.0f, 0.0f);
		
		if(mLightRotate)
			mLightCam->Yaw(dt * 0.5f);
	}

	//--------------------------------------------------------------------------
	void TFypApp::NextAlgorithm()
	{
		FinalCurrentAlgorithm();

		mCurrAlgorithm++;

		if( mCurrAlgorithm >= mAlgorithms.size() )
			mCurrAlgorithm = 0;

		InitCurrentAlgorithm();
	}

	//--------------------------------------------------------------------------
	void TFypApp::SetAlgorithm(size_t index)
	{
		FinalCurrentAlgorithm();
		
		if( index < mAlgorithms.size() )
			mCurrAlgorithm = index;

		InitCurrentAlgorithm();
	}

	//--------------------------------------------------------------------------
	void TFypApp::InitCurrentAlgorithm()
	{
		mAlgorithms[mCurrAlgorithm]->Init(mResPool, mGuiMgr);
		mAlgorithms[mCurrAlgorithm]->ChangeShadowMapSize( CurrentShadowMapSize() );

		UpdateDebugText();
	}
	
	//--------------------------------------------------------------------------
	void TFypApp::FinalCurrentAlgorithm()
	{
		mAlgorithms[mCurrAlgorithm]->Final(mResPool);
	}

	//--------------------------------------------------------------------------
	size_t TFypApp::CurrentShadowMapSize()
	{
		return mShadowMapSizes[mCurrShadowMapSize];
	}

	//--------------------------------------------------------------------------
	void TFypApp::NextShadowMapSize()
	{
		mCurrShadowMapSize++;

		if( mCurrShadowMapSize >= mShadowMapSizes.size() )
			mCurrShadowMapSize = 0;

		mAlgorithms[mCurrAlgorithm]->ChangeShadowMapSize( CurrentShadowMapSize() );

		UpdateDebugText();
	}

	//--------------------------------------------------------------------------
	void TFypApp::SetShadowMapSize(size_t index)
	{
		
		if( index < mShadowMapSizes.size() )
			mCurrShadowMapSize = index;

		mAlgorithms[mCurrAlgorithm]->ChangeShadowMapSize( CurrentShadowMapSize() );

		UpdateDebugText();
	}

	//--------------------------------------------------------------------------
	void TFypApp::UpdateDebugText()
	{
		//char buf[256];

		//sprintf(buf, "Algorithm = %s; ShadowMapSize = %d", 
		//	mAlgorithms[mCurrAlgorithm]->GetDescription(),
		//	CurrentShadowMapSize() );

		//SetWindowTextA(DXUTGetHWNDDeviceWindowed(), buf);
	}
	
	//--------------------------------------------------------------------------
}
}
